package ajaxjs.sql;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.script.ScriptException;
import javax.sql.DataSource;
import javax.naming.Context;
import javax.naming.NamingException;

import java.util.Properties;

import ajaxjs.Constant;
import ajaxjs.js.JsRuntime;

/**
 * 数据访问类
 * Data Access Object
 * @author frank
 */
public class DAO {
	
	/**
	 * 连接数据库。
	 * @param jdbcConn 数据库连接对象
	 */
	public DAO(Connection jdbcConn){
		this.jdbcConn = jdbcConn;
		checkIfNull();
	}
	
	/**
	 * 连接数据库（使用连接池）
	 * @param path 连接池的路径
	 */
	public DAO(String path){
		this.jdbcConn = getConn(path);
		checkIfNull();
	}
	
	/**
	 * 连接数据库（不使用连接池，而是传统方式）
	 * @param driver
	 * @param jdbcUrl
	 * @param props
	 */
	public DAO(String driver, String jdbcUrl, java.util.Properties props){
		this.jdbcConn = getConn(driver, jdbcUrl, props);
		checkIfNull();
	}
	
	/**
	 * 创建数据库连接对象（通过 TOMCAT 7 连接池 获取数据库连接）
	 * @param path Context.xml 配置文件中路径
	 * @return
	 */
	public static Connection getConn(String path){
		Connection conn = null;
		System.out.println(Constant.ConsoleDiver);
		
		try{
			Context envContext  = (Context)new javax.naming.InitialContext().lookup("java:/comp/env"); 
			DataSource ds = (DataSource)envContext.lookup(path); 
//			 简写方式
//			javax.sql.DataSource ds = (javax.sql.DataSource)new javax.naming.InitialContext().lookup("java:/comp/env/jdbc/derby"); 
			
			conn = ds.getConnection(); 
			
			System.out.println("成功数据库连接" + Constant.printObject_or_status +  conn.getMetaData().getURL());
		}catch(SQLException e){
			System.out.println("数据库连接失败！ path:" + path); 
			e.printStackTrace();
		}catch (NamingException e) {
			System.out.println("读取数据源的配置文件失败，请检查 Tomcat 连接池配置"); 
			e.printStackTrace();
		}
		
		return conn;
	}

	/**
	 * 创建数据库连接对象（传统方式）
	 * @param driver e.g "com.mysql.jdbc.Driver"
	 * @param jdbcUrl e.g "jdbc:mysql://linux-db-bjzwNew2.xincache.cn:3306/net25963067?user=net25963067&password=2crF0n0x"
	 * @param props
	 * @return
	 */
	public static Connection getConn(String driver, String jdbcUrl, Properties props){
		Connection conn = null;
		
		try {
			Class.forName(driver);
		} catch (ClassNotFoundException e) {
			System.out.println("创建数据库连接失败，请检查是否安装对应的 Driver"); 
			e.printStackTrace();
		}
		
		try {
			if(props == null){
				conn = DriverManager.getConnection(jdbcUrl);
			}else{
				conn = DriverManager.getConnection(jdbcUrl, props);
			}
			System.out.println("数据库连接成功：" + conn);
		} catch (SQLException e) {
			System.out.println("数据库连接失败！"); 
			e.printStackTrace();
		}
		
		return conn;
	}
	
	/**
	 * 关闭数据库连接
	 */
	public void closeConnection(Connection jdbcConn){
		try {
			jdbcConn.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 关闭数据库连接
	 */
	public void closeConnection(){
		try {
			this.jdbcConn.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 检查数据库连接对象是否非空。
	 */
	private void checkIfNull(){
		if(this.jdbcConn == null){
			System.out.println("数据库 connection 对象 null point!");
		}
	}
	
	public Connection jdbcConn = null;

    public interface Callback {  
        Object doIt(ResultSet resultset) throws SQLException;  
    } 
	
	/**
	 * SQL 的 SELECT 查询，既返回 RS 结果集合，也可以返回 SELECT COUNT(id) AS total 的总数。
	 * 这是读操作的查询。
	 * @param {String} sql SELECT语句
	 * @param {Booelan} isCount 是否返回总数
	 * @returns {Object} 可变返回值，既可是 RS 结果集，也可以是 int 总数，使用强类型转换获取真实类型。
	 */
	public Object query(String sql, boolean isCount, Callback cb){
		Object arr = null;
		Statement statement = null;
		ResultSet resultset = null;
				
//		System.out.println("将要查询的 SQL 为:" + sql);
		
		try{
			statement = jdbcConn.createStatement();
			resultset = statement.executeQuery(sql);
			
			if(isCount){
				if(jdbcConn.toString().indexOf("MySQL") != -1 || jdbcConn.toString().indexOf("mysql") != -1){
					arr = resultset.next() ?  resultset.getInt(1) : null;
				}else{
					// sqlite
					arr = resultset.isBeforeFirst() ? resultset.getInt(1) : null;
				}
						
				// cover to js number, but it doesn't work here.
//				arr = jsEngine.call("Number", arr);
			}else{
				// 如果没有符合的记录，返回 null 
				if(resultset.isBeforeFirst() && cb != null){
					//arr = jsEngine.call("getValue", resultset);
					cb.doIt(resultset);
				}
			}
			
			if(isCount == false && cb == null){
				arr = resultset;// 返回 resultset
			}
			
//			System.out.println("查询成功！:::");
		}catch(Throwable e){
			System.out.println("查询失败！:::" + sql);
			e.printStackTrace();
		}finally{
			if(isCount == false && cb == null){
				// 返回 resultset 不要释放 resultset
				release(statement, null);
			}else release(statement, resultset);
		}
		
		return arr;
	}
	
	/**
	 * SQL 的 SELECT 查询，返回 RS 结果集合
	 * @param sql
	 * @return
	 */
	public ResultSet query(String sql){
		return (ResultSet)this.query(sql, false, null);
	}
	
	/**
	 * 支持回调的查询
	 * @param sql
	 * @param cb
	 * @return
	 */
	public Object query(String sql, Callback cb){
		return this.query(sql, false, cb);
	}
	
	/**
	 * 用于记录总数的返回
	 * @param sql
	 * @param isCount
	 * @return
	 */
	public int query(String sql, boolean isCount){
		return (Integer)this.query(sql, true, null);
	}
	private Object JSON_arr;
	public void getJSON_List(Object arr){
		JSON_arr = arr;
	}
	
	/**
	 * 返回 JSON 格式数据
	 * @param sql
	 * @param jsEngine
	 * @return
	 */
	public Object queryAsJSON(String sql, final JsRuntime jsEngine){
		this.query(sql, false, new Callback(){
			public Object doIt(ResultSet resultset){
				try {
					getJSON_List(jsEngine.call("getValue", resultset));
				} catch (ScriptException e) {
					e.printStackTrace();
					System.err.println("RS 转换为 json 时失败！");
				}
				return null;
			}
		});
		return JSON_arr;
	}	
	
	/**
	 * 这是写操作的查询。
	 * @param {String} sql
	 * @returns {Number} 返回影响的行数
	 */
	public int executeUpdate(String sql){
		Statement statement = null;
		int rowNumber = 0;
//		System.out.println("将要执行写操作:" + sql);
		try{
			statement = jdbcConn.createStatement();
			
			if(sql.contains("INSERT INTO")){
//				rowNumber = statement.executeUpdate(sql, Statement.RETURN_GENERATED_KEYS);
				rowNumber = statement.executeUpdate(sql);
				ResultSet rs = statement.getGeneratedKeys();// 创建新纪录，返回id
	            if (rs.next()) {  
	            	rowNumber = rs.getInt(1);   
//		                System.out.println("数据主键：" + id);   
	            } 
			}else rowNumber = statement.executeUpdate(sql);
			
			System.out.println("写操作成功！:::");
		}catch(Throwable e){
			System.out.println("写操作失败！:::" + sql);
			e.printStackTrace();
		}finally{
			release(statement, null);
		}
		
		return rowNumber;
	}
	
	/**
	 * SQL 的 SELECT 查询，返回 RS 结果集合（List类型）
	 * @param connection
	 * @param sql
	 * @return
	 */
	public List<Map<String, String>> queryAsList(String sql){
		Statement statement = null;
		ResultSet resultset = null;
//		System.out.println("将要查询的 SQL 为:" + sql);
		
		List<Map<String, String>> arr = new ArrayList<Map<String, String>>();
		
		try{
			statement = jdbcConn.createStatement();
			resultset = statement.executeQuery(sql);
			// 如果没有符合的记录，返回 null 
			if(resultset.isBeforeFirst()){
				ResultSetMetaData metaData = resultset.getMetaData();
				
				while(resultset.next()){
					Map<String, String> record = new HashMap<String, String>();
					for(int i = 0, j = metaData.getColumnCount(); i < j; i++) {
						int colIndex = i + 1; // 列数从 1 开始
						String columnName = metaData.getColumnName(colIndex);
						record.put(columnName, resultset.getString(columnName));
					}
					arr.add(record);
				}
			}else{
				System.out.println("no data");
				return null;
			}
		}catch(Throwable e){
			System.out.println("查询失败！:::" + sql);
			e.printStackTrace();
		}finally{
			release(statement, resultset);
		}
		
		return arr;
	}
	
	/**
	 * 是否数据库资源
	 * @param statement
	 * @param resultset
	 */
	private static void release(Statement statement, ResultSet resultset){
		if(statement != null)
			try {
				statement.close();
			} catch (SQLException e) {}
		
		if(resultset != null)
			try {
				resultset.close();
			} catch (SQLException e) {}
	}
}
